using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using Wombat.Common;
using Wombat.Core.Services;
using Wombat.Data.Model;

namespace Wombat.Data
{
    public class StreamRepository : IStreamRepository
    {
        private readonly IDataService _dataService;
        public bool BulkInsert { get; set; }
        public TransactionScopeOption TransactionScopeOption { get; set; }
        public TimeSpan BulkInsertQueueTime { get; set; }
        public int BulkInsertQueueLength { get; set; }

        public StreamRepository(IDataService dataService)
        {
            _dataService = dataService;
            TransactionScopeOption = TransactionScopeOption.Required;
            _floatQueue = new EnqueueAndInsert<float>(this, i => _dataService.BulkInsertFloatData(
                i.Select(GetFloatData)));
        }

        public void InsertStream<T>(IObservable<ITimed<T>> stream)
        {
            ArgumentCheck.NotNull(stream, "stream");
            if (BulkInsert)
            {
                if (BulkInsertQueueLength == 0 || BulkInsertQueueTime == default(TimeSpan))
                    throw new InvalidOperationException(Strings.StreamRepository_BothBulkInsertParamsShouldBeSet);
                stream.Subscribe(QueueAndBulkInsert, ErrorHappened, Close);
            }
            else
            {
                stream.Subscribe(Insert, ErrorHappened, Close);
            }
        }

        private void Close()
        {
            _floatQueue.Close();
        }

        private void ErrorHappened(Exception exception)
        {
            throw new NotImplementedException();
        }

        private readonly EnqueueAndInsert<float> _floatQueue;
        private void QueueAndBulkInsert<T>(ITimed<T> tv)
        {
            _floatQueue.Enqueue((ITimed<float>)tv);
        }

        private class EnqueueAndInsert<T>
        {
            private readonly StreamRepository _owner;
            private readonly Action<IEnumerable<ITimed<T>>> _bulkInserter;
            private readonly Queue<ITimed<T>> _queueFloat = new Queue<ITimed<T>>();

            public EnqueueAndInsert(StreamRepository rep, Action<IEnumerable<ITimed<T>>> bulkInserter)
            {
                _owner = rep;
                _bulkInserter = bulkInserter;
            }

            public void Enqueue(ITimed<T> tv)
            {
                _queueFloat.Enqueue(tv);
                var queuLength = tv.Time - _queueFloat.Peek().Time;
                if (queuLength >= _owner.BulkInsertQueueTime || _queueFloat.Count >= _owner.BulkInsertQueueLength)
                {
                    _bulkInserter(_queueFloat);
                    _queueFloat.Clear();
                }
            }

            public void Close()
            {
                if (_queueFloat.Count <= 0) return;
                _bulkInserter(_queueFloat);
                _queueFloat.Clear();
            }
        }

        private void Insert<T>(ITimed<T> timeValue)
        {
            using (var transaction = new TransactionScope(TransactionScopeOption))
            {
                InsertFloat(timeValue);
                transaction.Complete()  ;
            }

        }

        private void InsertFloat<T>(ITimed<T> timeValue)
        {
            var data = GetFloatData(timeValue);
            _dataService.InsertFloatData(data);
        }

        private static FloatData GetFloatData<T>(ITimed<T> timeValue)
        {
            var data = new FloatData {TagId = timeValue.TagId, Time = timeValue.Time};
            data.Value = data.Value;
            return data;
        }
    }
}